#include <ctype.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "rmath.h"
#include "redintobject.h"
#include "redmapobject.h"
#include "redboolobject.h"
#include "redarrayobject.h"
#include "redtupleobject.h"
#include "redstringobject.h"
#include "redexceptionobject.h"

#define cache_def()													\
	int cp = 0;														\
	char cache[256] = {0};

#define cache_char(c)												\
	do																\
	{																\
		cache[cp] = (c);											\
		if (++cp == 255)											\
			cache_flush();											\
	} while (0)

#define cache_flush()												\
	do																\
	{																\
		if (cp)														\
		{															\
			cache[cp] = 0;											\
			RedObject *_t = RedString_AppendCString(result, cache);	\
			Red_UNREF(result);										\
																	\
			cp = 0;													\
			result = _t;											\
		}															\
	} while (0)

static RedObject *string_len(RedObject *self)
{
	return RedInt_FromInt(RedString_Length(self));
}

static RedObject *string_str(RedObject *self)
{
	Red_REF(self);
	return self;
}

static uint32_t string_hash(RedObject *self)
{
	return rmath_hash_str(RedString_Buffer(self), RedString_Length(self));
}

static RedObject *string_repr(RedObject *self)
{
	int n = RedString_Length(self);
	char *p = RedString_Buffer(self);

	cache_def();
	char quote = '\'';

	if (memchr(p, '\'', n) && !memchr(p, '"', n))
		quote = '"';

	RedObject *result = RedString_FromChar(quote);

	while (n--)
	{
		char ch = *p++;

		if (ch == quote)
		{
			cache_char('\\');
			cache_char(ch);
			continue;
		}

		switch (ch)
		{
			case '\\':
			{
				cache_char('\\');
				cache_char('\\');
				break;
			}

			case '\t':
			{
				cache_char('\\');
				cache_char('t');
				break;
			}

			case '\n':
			{
				cache_char('\\');
				cache_char('n');
				break;
			}

			case '\r':
			{
				cache_char('\\');
				cache_char('r');
				break;
			}

			default:
			{
				if (ch >= ' ' && ch < 0x7f)
				{
					cache_char(ch);
					break;
				}

#define to_hex(n)	(((n) < 10) ? ((n) + '0') : ((n) - 10 + 'a'))

				cache_char('\\');
				cache_char('x');
				cache_char(to_hex(((ch & 0xF0) >> 4)));
				cache_char(to_hex(((ch & 0x0F) >> 0)));
				break;

#undef to_hex
			}
		}
	}

	cache_char(quote);
	cache_flush();

	return result;
}

static RedObject *string_eq(RedObject *self, RedObject *other)
{
	if (RedString_Equals(self, other))
		Red_Return_True();
	else
		Red_Return_False();
}

static RedObject *string_neq(RedObject *self, RedObject *other)
{
	if (!RedString_Equals(self, other))
		Red_Return_True();
	else
		Red_Return_False();
}

static RedObject *string_mod(RedObject *self, RedObject *other)
{
	if (RedTuple_Exact(other))
		return RedString_Format(self, other);

	RedObject *args = RedTuple_FromObjects(1, other);
	RedObject *result = RedString_Format(self, args);

	Red_UNREF(args);
	return result;
}

static RedObject *string_find(RedObject *self, RedObject *args)
{
	int len;
	char *str;
	char *result;

	if (!RedTuple_Unpack(args, "s@", &len, &str))
		return NULL;

	if ((RedString_Length(self) > len) ||
		!(result = strstr(RedString_Buffer(self), str)))
		return RedInt_FromInt(-1);
	else
		return RedInt_FromInt(result - str);
}

/* the standard library doesn't have `strrstr`, we implement it ourself */
static char *strrstr(char *s, const char *s1)
{
	char *r = NULL;
	char *p = NULL;

	if (!*s1)
		return s + strlen(s);
	
	while ((p = strstr(s, s1)))
	{
		r = p;
		s = p + 1;
	}

	return r;
}

static RedObject *string_rfind(RedObject *self, RedObject *args)
{
	int len;
	char *str;
	char *result;

	if (!RedTuple_Unpack(args, "s@", &len, &str))
		return NULL;

	if ((RedString_Length(self) > len) ||
		!(result = strrstr(RedString_Buffer(self), str)))
		return RedInt_FromInt(-1);
	else
		return RedInt_FromInt(result - str);
}

static RedObject *string_lower(RedObject *self, RedObject *args)
{
	return RedTuple_Unpack(args, "") ? RedString_Lower(self) : NULL;
}

static RedObject *string_upper(RedObject *self, RedObject *args)
{
	return RedTuple_Unpack(args, "") ? RedString_Upper(self) : NULL;
}

static RedObject *string_strip(RedObject *self, RedObject *args)
{
	return RedTuple_Unpack(args, "") ? RedString_Strip(self) : NULL;
}

static RedObject *string_split(RedObject *self, RedObject *args)
{
	int len;
	char *sp;

	if (!RedTuple_Unpack(args, "s@", &len, &sp))
		return NULL;

	if (RedString_Length(self) < len)
		return RedArray_Create();

	char *p = NULL;
	char *s = RedString_Buffer(self);
	char *x = RedString_Buffer(self);
	RedObject *result = RedArray_Create();

	while ((p = strstr(s, sp)))
	{
		RedObject *item = RedString_FromBufferAndSize(s, (int)(p - s + 1));

		if (!RedArray_Append(result, item))
		{
			Red_UNREF(item);
			Red_UNREF(result);
			return NULL;
		}

		s += len;
		Red_UNREF(item);
	}

	RedObject *item = RedString_FromBufferAndSize(s, (int)(len + x - s));

	if (!RedArray_Append(result, item))
	{
		Red_UNREF(item);
		Red_UNREF(result);
		return NULL;
	}

	Red_UNREF(item);
	return result;
}

static RedObject *string_append(RedObject *self, RedObject *args)
{
	RedObject *string;
	return RedTuple_Unpack(args, "s#", &string) ? RedString_Append(self, string) : NULL;
}

static RedObject *string_format(RedObject *self, RedObject *args)
{
	if (RedTuple_Length(args) < 1)
	{
		RedException_ThrowFormat(&RedExc_ArgumentError, "Function takes at least 1 argument, but no args given");
		return NULL;
	}

	RedObject *arg0 = RedTuple_Items(args)[0];
	return RedString_Format(self, (RedTuple_Exact(arg0) ? arg0 : args));
}

static RedObject *string_endswith(RedObject *self, RedObject *args)
{
	RedObject *string;
	return RedTuple_Unpack(args, "s#", &string) ? RedString_EndsWith(self, string) : NULL;
}

static RedObject *string_startswith(RedObject *self, RedObject *args)
{
	RedObject *string;
	return RedTuple_Unpack(args, "s#", &string) ? RedString_StartsWith(self, string) : NULL;
}

static RedObject *string_splitlines(RedObject *self, RedObject *args)
{
	if (!RedTuple_Unpack(args, ""))
		return NULL;

	int i = 0;
	int j = 0;
	int n = RedString_Length(self);
	char *s = RedString_Buffer(self);
	RedObject *result = RedArray_Create();

	for (;;)
	{
		/* search for '\n' or '\r\n' */
		while (i < n && s[i] != '\r' && s[i] != '\n')
			i++;

		if (i == n)
		{
			/* anything remains */
			RedObject *line = RedString_FromBufferAndSize(s + j, i - j);

			RedArray_Append(result, line);
			Red_UNREF(line);
			return result;
		}
		else
		{
			if (s[i] == '\n')
			{
				i++;					/* '\n' */
			}
			else if (s[i] == '\r')
			{
				if (s[i + 1] != '\n')
					i++;				/* '\r' */
				else
					i += 2;				/* '\r\n' */
			}

			/* dump the line */
			RedObject *line = RedString_FromBufferAndSize(s + j, i - j);

			j = i;
			RedArray_Append(result, line);
			Red_UNREF(line);
		}
	}
}

static RedMethodDesc string_methods[] =
{
	{ "find"		, string_find		},
	{ "rfind"		, string_rfind		},
	{ "lower"		, string_lower		},
	{ "upper"		, string_upper		},
	{ "strip"		, string_strip		},
	{ "split"		, string_split		},
	{ "append"		, string_append		},
	{ "format"		, string_format		},
	{ "endswith"	, string_endswith	},
	{ "startswith"	, string_startswith	},
	{ "splitlines"	, string_splitlines	},
	{ NULL, NULL }
};

RedTypeObject RedType_String =
{
	RedType_INIT("string", RedStringObject, RedType_Object)

	.tp_len		= string_len,
	.tp_str		= string_str,
	.tp_hash	= string_hash,
	.tp_repr	= string_repr,

	.tp_eq		= string_eq,
	.tp_neq		= string_neq,

	.tp_mod		= string_mod,
	.tp_add		= RedString_Append,

	.tp_aug_mod = string_mod,
	.tp_aug_add	= RedString_Append,

	.tp_methods	= string_methods,
};

static RedObject *string_pool = NULL;

void Red_FreeStringPool(void)
{
	Red_UNREF(string_pool);
}

char Red_InitStringPool(void)
{
	string_pool = RedPool_Create();
	return string_pool != NULL;
}

RedObject *RedString_FromChar(char ch)
{
	RedObject *result = RedObject_CreateVar(&RedType_String, NULL, 2);

	if (!result)
		return NULL;

	RedString_Length(result) = 1;
	RedString_Buffer(result)[1] = 0;
	RedString_Buffer(result)[0] = ch;
	return result;
}

RedObject *RedString_FromLength(int length)
{
	RedObject *result = RedObject_CreateVar(&RedType_String, NULL, length + 1);

	if (!result)
		return NULL;

	RedString_Length(result) = length;
	RedString_Buffer(result)[length] = 0;
	return result;
}

RedObject *RedString_FromRString(rstring_t *s)
{
	RedObject *result = NULL;

	/* lookup from pool */
	if (s->length <= RED_POOL_INTERN_MAX)
		if ((result = RedPool_GetString(string_pool, s->content, s->length)))
			return result;

	/* not found, create one */
	result = RedObject_CreateVar(&RedType_String, NULL, s->length + 1);

	if (!result)
		return NULL;

	/* cache it up */
	if (s->length <= RED_POOL_INTERN_MAX)
		RedPool_PutString(string_pool, s->content, s->length, result);

	RedString_Length(result) = s->length;
	RedString_Buffer(result)[s->length] = 0;
	memcpy(RedString_Buffer(result), s->content, s->length);
	return result;
}

RedObject *RedString_FromCString(const char *s)
{
	int len = (int)strlen(s);
	RedObject *result = NULL;

	/* lookup from pool */
	if (len <= RED_POOL_INTERN_MAX)
		if ((result = RedPool_GetString(string_pool, s, len)))
			return result;

	/* not found, create one */
	result = RedObject_CreateVar(&RedType_String, NULL, len + 1);

	if (!result)
		return NULL;

	/* cache it up */
	if (len <= RED_POOL_INTERN_MAX)
		RedPool_PutString(string_pool, s, len, result);

	RedString_Length(result) = len;
	RedString_Buffer(result)[len] = 0;
	memcpy(RedString_Buffer(result), s, len);
	return result;
}

RedObject *RedString_FromCFormat(const char *format, ...)
{
	va_list args;
	va_start(args, format);
	return RedString_FromCFormatVa(format, args);
}

RedObject *RedString_FromCFormatVa(const char *format, va_list args)
{
	va_list copy;
	va_copy(copy, args);

	int len = vsnprintf(NULL, 0, format, copy);
	RedObject *result = RedObject_CreateVar(&RedType_String, NULL, len + 1);

	if (!result)
	{
		va_end(args);
		va_end(copy);
		return NULL;
	}

	RedString_Length(result) = len;
	RedString_Buffer(result)[len] = 0;
	vsnprintf(RedString_Buffer(result), len + 1, format, args);

	va_end(args);
	va_end(copy);
	return result;
}

RedObject *RedString_FromBufferAndSize(const char *buffer, int length)
{
	RedObject *result = RedObject_CreateVar(&RedType_String, NULL, length + 1);

	if (!result)
		return NULL;

	RedString_Length(result) = length;
	RedString_Buffer(result)[length] = 0;
	memcpy(RedString_Buffer(result), buffer, length);
	return result;
}

RedObject *RedString_Lower(RedObject *self)
{
	if (!RedString_Exact(self))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'string' expected for 'self' argument");
		return NULL;
	}

	RedObject *result = RedString_FromLength(RedString_Length(self));

	if (!result)
		return NULL;

	for (int i = 0; i < RedString_Length(self); i++)
		RedString_Buffer(result)[i] = tolower(RedString_Buffer(self)[i]);

	return result;
}

RedObject *RedString_Upper(RedObject *self)
{
	if (!RedString_Exact(self))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'string' expected for 'self' argument");
		return NULL;
	}

	RedObject *result = RedString_FromLength(RedString_Length(self));

	if (!result)
		return NULL;

	for (int i = 0; i < RedString_Length(self); i++)
		RedString_Buffer(result)[i] = toupper(RedString_Buffer(self)[i]);

	return result;
}

RedObject *RedString_Strip(RedObject *self)
{
	if (!RedString_Exact(self))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'string' expected for 'self' argument");
		return NULL;
	}

	int i = 0;
	int j = RedString_Length(self) - 1;
	char *s = RedString_Buffer(self);

	while (i <= j && isspace(s[i])) i++; /* strip left */
	while (i <= j && isspace(s[j])) j--; /* strip right */

	return RedString_FromBufferAndSize(s + i, j - i + 1);
}

RedObject *RedString_Append(RedObject *self, RedObject *other)
{
	if (!RedString_Exact(self))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'string' expected for 'self' argument");
		return NULL;
	}

	if (!RedString_Exact(other))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'string' expected for 'other' argument");
		return NULL;
	}

	int len1 = RedString_Length(self);
	int len2 = RedString_Length(other);
	RedObject *result = RedObject_CreateVar(&RedType_String, NULL, len1 + len2 + 1);

	if (!result)
		return NULL;

	RedString_Length(result) = len1 + len2;
	RedString_Buffer(result)[len1 + len2] = 0;
	memcpy(RedString_Buffer(result), RedString_Buffer(self), len1);
	memcpy(&RedString_Buffer(result)[len1], RedString_Buffer(other), len2);
	return result;
}

RedObject *RedString_Format(RedObject *self, RedObject *items)
{
	if (!RedString_Exact(self))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'string' expected for 'self' argument");
		return NULL;
	}

	if (!RedTuple_Exact(items))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'tuple' expected for 'items' argument");
		return NULL;
	}

	int i = 0;
	int n = RedString_Length(self);
	char *p = RedString_Buffer(self);

	cache_def();
	RedObject *result = RedString_FromLength(0);

#define next_arg()		(i < RedTuple_Length(items) ? RedTuple_Items(items)[i++] : NULL)

	while (n--)
	{
		char ch = *p++;

		if (ch != '%')
		{
			/* cache it up for avoiding frequently malloc/free */
			cache_char(ch);
			continue;
		}

		ch = *p++;
		cache_flush();

		switch (ch)
		{
			case '%':
			{
				cache[cp++] = ch;
				break;
			}

			case 'r':
			{
				RedObject *arg = next_arg();

				if (!arg)
				{
					Red_UNREF(result);
					RedException_ThrowFormat(&RedExc_ArgumentError, "Not enough arguments for string formatting");
					return NULL;
				}

				RedObject *v = RedObject_Repr(arg);
				RedObject *temp = RedString_Append(result, v);

				Red_UNREF(v);
				Red_UNREF(result);

				result = temp;
				break;
			}

			case 's':
			{
				RedObject *arg = next_arg();

				if (!arg)
				{
					Red_UNREF(result);
					RedException_ThrowFormat(&RedExc_ArgumentError, "Not enough arguments for string formatting");
					return NULL;
				}

				if (!RedString_Exact(arg))
				{
					Red_UNREF(result);
					RedException_ThrowFormat(&RedExc_TypeError, "'string' expected for '%s' format");
					return NULL;
				}

				RedObject *temp = RedString_Append(result, arg);

				Red_UNREF(result);
				result = temp;
				break;
			}

			case 'd':
			{
				RedObject *arg = next_arg();

				if (!arg)
				{
					Red_UNREF(result);
					RedException_ThrowFormat(&RedExc_ArgumentError, "Not enough arguments for string formatting");
					return NULL;
				}

				if (!RedInt_Exact(arg))
				{
					Red_UNREF(result);
					RedException_ThrowFormat(&RedExc_TypeError, "'int' expected for '%d' format");
					return NULL;
				}

				RedObject *v = RedString_FromCFormat("%lld", RedInt_Value(arg));
				RedObject *temp = RedString_Append(result, v);

				Red_UNREF(v);
				Red_UNREF(result);

				result = temp;
				break;
			}

			case 'p':
			{
				char buffer[32] = {0};
				RedObject *arg = next_arg();

				if (!arg)
				{
					Red_UNREF(result);
					RedException_ThrowFormat(&RedExc_ArgumentError, "Not enough arguments for string formatting");
					return NULL;
				}

				snprintf(buffer, 32, "%p", arg);
				RedObject *temp = RedString_AppendCString(result, buffer);

				Red_UNREF(result);
				result = temp;
				break;
			}

			default:
			{
				RedException_ThrowFormat(&RedExc_FormatError, "Unrecognized format directive '%c'", ch);
				Red_UNREF(result);
				return NULL;
			}
		}
	}
	
#undef next_arg

	cache_flush();
	return result;
}

RedObject *RedString_EndsWith(RedObject *self, RedObject *tail)
{
	if (!RedString_Exact(self))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'string' expected for 'self' argument");
		return NULL;
	}

	if (!RedString_Exact(tail))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'string' expected for 'tail' argument");
		return NULL;
	}

	if (RedString_Length(self) < RedString_Length(tail))
		Red_Return_False();

	int i = RedString_Length(self);
	int j = RedString_Length(tail);

	char *pi = RedString_Buffer(self);
	char *pj = RedString_Buffer(tail);

	while (i > 0 && j > 0)
		if (pi[--i] != pj[--j])
			Red_Return_False();

	Red_Return_True();
}

RedObject *RedString_StartsWith(RedObject *self, RedObject *head)
{
	if (!RedString_Exact(self))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'string' expected for 'self' argument");
		return NULL;
	}

	if (!RedString_Exact(head))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'string' expected for 'head' argument");
		return NULL;
	}

	if (RedString_Length(self) < RedString_Length(head))
		Red_Return_False();

	int i = 0;
	int j = 0;
	char *pi = RedString_Buffer(self);
	char *pj = RedString_Buffer(head);

	while (i < RedString_Length(self) &&
		   j < RedString_Length(head))
		if (pi[i++] != pj[j++])
			Red_Return_False();

	Red_Return_True();
}

RedObject *RedString_AppendCString(RedObject *self, const char *s)
{
	RedObject *str = RedString_FromCFormat(s);
	RedObject *result = RedString_Append(self, str);

	Red_UNREF(str);
	return result;
}

RedObject *RedString_AppendCFormat(RedObject *self, const char *format, ...)
{
	va_list args;
	va_start(args, format);

	RedObject *str = RedString_FromCFormatVa(format, args);
	RedObject *result = RedString_Append(self, str);

	Red_UNREF(str);
	return result;
}

void RedString_Eat(RedObject *self, size_t length)
{
	if (length >= RedString_Length(self))
	{
		RedString_Length(self) = 0;
		return;
	}

	char *d = RedString_Buffer(self);
	char *s = RedString_Buffer(self) + length;

	RedString_Length(self) -= length;
	memmove(d, s, RedString_Length(self));
}

char RedString_Equals(RedObject *self, RedObject *other)
{
	return ((self == other) ||
		(RedString_Exact(self) &&
		 RedString_Exact(other) &&
		 (RedObject_Hash(self) == RedObject_Hash(other)) &&
		 (RedString_Length(self) == RedString_Length(other)) &&
		 !memcmp(RedString_Buffer(self), RedString_Buffer(other), RedString_Length(self))));
}

char RedString_EqualsCString(RedObject *self, const char *s)
{
	int len = (int)strlen(s);
	return RedString_Exact(self) && (len == RedString_Length(self)) && !memcmp(RedString_Buffer(self), s, len);
}
